use std::collections::VecDeque;

use logos::Logos;

use super::lexer::{ChemToken, ChemicalLexer, ExprToken, LexicalError};

#[allow(dead_code)]
struct ExprParser {
    tokens: Vec<ExprToken>,
    pos: usize,
}

impl ExprParser {
    fn new(input: &str) -> Self {
        let tokens = ExprToken::lexer(input)
            .filter_map(|token| token.ok())
            .collect();
        Self { tokens, pos: 0 }
    }

    // fn peek(&mut self) -> Option<&ExprToken> {
    //     self.tokens.get(self.pos)
    // }

    // fn next(&mut self) -> Option<&ExprToken> {
    //     self.pos += 1;
    //     self.tokens.get(self.pos - 1)
    // }
}

/// parsing expression with chemical and get dalton
/// ```rust
/// use dalton_lib::parse;
///
/// let dalton = parse("#CuSO4# * 1 + <H2O> * (2 * 3) - 'H2O'").unwrap();
/// assert!((dalton - 249.6850).abs() < 1E-3);
/// ```
/// use a pair of #, ' or angle brackets to wrap chemcials and combine with other expressions
pub fn parse(input: &str) -> Result<f64, ParseError<usize, ChemToken, LexicalError>> {
    // let mut lexer = ExprParser::new(input);
    // expr_bp(&mut lexer, 0)
    let lexer = ExprParser::new(input);
    stack_parse(lexer)
}

fn stack_parse(lexer: ExprParser) -> Result<f64, ParseError<usize, ChemToken, LexicalError>> {
    let mut values = VecDeque::new();
    let mut ops = VecDeque::new();

    for token in lexer.tokens.iter() {
        match token {
            ExprToken::Number(n) | ExprToken::Chemical(n) => values.push_back(*n),
            ExprToken::Lparent => ops.push_back(token),
            ExprToken::Rparent => {
                while let Some(op) = ops.pop_back() {
                    if *op == ExprToken::Lparent {
                        break;
                    }
                    if let Some(rhs) = values.pop_back() {
                        if let Some(lhs) = values.pop_back() {
                            let result = op.apply(rhs, lhs);
                            values.push_back(result);
                        } else {
                            return Err(ParseError::User {
                                error: LexicalError::InvalidToken,
                            });
                        }
                    }
                }
            }
            op @ (ExprToken::Add | ExprToken::Sub | ExprToken::Mul | ExprToken::Div) => {
                while let Some(top_op) = ops.back() {
                    if top_op.precedence() >= op.precedence() {
                        if let Some(rhs) = values.pop_back() {
                            if let Some(lhs) = values.pop_back() {
                                let result = top_op.apply(rhs, lhs);
                                values.push_back(result);
                                ops.pop_back();
                            } else {
                                return Err(ParseError::User {
                                    error: LexicalError::InvalidToken,
                                });
                            }
                        }
                    } else {
                        break;
                    }
                }
                ops.push_back(op);
            }
        }
    }
    while let Some(op) = ops.pop_back() {
        if let Some(rhs) = values.pop_back() {
            if let Some(lhs) = values.pop_back() {
                let result = op.apply(rhs, lhs);
                values.push_back(result);
            } else {
                return Err(ParseError::User {
                    error: LexicalError::InvalidToken,
                });
            }
        }
    }
    Ok(values.pop_back().unwrap())
}

// fn expr_bp(
//     lexer: &mut ExprParser,
//     min_bp: u8,
// ) -> Result<f64, ParseError<usize, ChemToken, LexicalError>> {
//     let mut lhs = match lexer.next() {
//         Some(ExprToken::Number(num)) => *num,
//         Some(ExprToken::Lparent) => {
//             let lhs = expr_bp(lexer, 0)?;
//             if lexer.next() != Some(&ExprToken::Rparent) {
//                 return Err(ParseError::InvalidToken {
//                     location: lexer.pos,
//                 });
//             }
//             lhs
//         }
//         Some(ExprToken::Chemical(chemical)) => *chemical,
//         _ => {
//             return Err(ParseError::InvalidToken {
//                 location: lexer.pos,
//             })
//         }
//     };
//     loop {
//         let op = match lexer.peek() {
//             None | Some(ExprToken::Rparent) => break, // EOF
//             Some(ExprToken::Add) => '+',
//             Some(ExprToken::Sub) => '-',
//             Some(ExprToken::Mul) => '*',
//             Some(ExprToken::Div) => '/',
//             _ => {
//                 return Err(ParseError::InvalidToken {
//                     location: lexer.pos + 1,
//                 })
//             }
//         };

//         if let Some((l_bp, r_bp)) = infix_binding_power(op) {
//             if l_bp < min_bp {
//                 break;
//             }
//             lexer.next();
//             lhs = {
//                 let rhs = expr_bp(lexer, r_bp)?;
//                 match op {
//                     '+' => lhs + rhs,
//                     '-' => lhs - rhs,
//                     '*' => lhs * rhs,
//                     '/' => lhs / rhs,
//                     t => panic!("bad token: {:?}", t),
//                 }
//             }
//         }
//     }
//     Ok(lhs)
// }

// fn infix_binding_power(op: char) -> Option<(u8, u8)> {
//     let res = match op {
//         '+' | '-' => (5, 6),
//         '*' | '/' => (7, 8),
//         _ => return None,
//     };
//     Some(res)
// }

use lalrpop_util::{lalrpop_mod, ParseError};
lalrpop_mod!(chemical);
use chemical::ChemicalParser;

/// parsing chemical and get dalton
/// ```rust
/// use dalton_lib::chem_parse;
///
/// let dalton = chem_parse("CuSO4 5H2O").unwrap();
/// assert!((dalton - 249.6850).abs() < 1E-3);
/// ```
pub fn chem_parse(chemical: &str) -> Result<f64, ParseError<usize, ChemToken, LexicalError>> {
    let chemical_lexer = ChemicalLexer::new(chemical);
    let chemical_parser = ChemicalParser::new();
    // parse_total(&mut chemical_lexer)
    chemical_parser.parse(chemical_lexer)
}
